#include <crt_rwlock.h>

#if (TARGET_OS == OS_WINDOWS)

int _rwlock_init(_rwlock_t* rwlock) {
	/* Initialize the semaphore that acts as the write lock. */
	HANDLE handle = CreateSemaphoreW(NULL, 1, 1, NULL);
	if (handle == NULL)
		return -1;

	rwlock->state_.write_semaphore_ = handle;
	/* Initialize the critical section protecting the reader count. */
	InitializeCriticalSection(&rwlock->state_.num_readers_lock_);

	/* Initialize the reader count. */
	rwlock->state_.num_readers_ = 0;

	return 0;
}

void _rwlock_destroy(_rwlock_t* rwlock) {
	DeleteCriticalSection(&rwlock->state_.num_readers_lock_);
	CloseHandle(rwlock->state_.write_semaphore_);
}

void _rwlock_rdlock(_rwlock_t* rwlock) {
	/* Acquire the lock that protects the reader count. */
	EnterCriticalSection(&rwlock->state_.num_readers_lock_);

	/* Increase the reader count, and lock for write if this is the first
	* reader.
	*/
	if (++rwlock->state_.num_readers_ == 1) {
		DWORD r = WaitForSingleObject(rwlock->state_.write_semaphore_, INFINITE);
		if (r != WAIT_OBJECT_0)
			os_fatal_error(GetLastError(), "WaitForSingleObject");
	}

	/* Release the lock that protects the reader count. */
	LeaveCriticalSection(&rwlock->state_.num_readers_lock_);
	}

int _rwlock_tryrdlock(_rwlock_t* rwlock) {
	int err;

	if (!TryEnterCriticalSection(&rwlock->state_.num_readers_lock_))
		return -1;

	err = 0;

	if (rwlock->state_.num_readers_ == 0) {
		/* Currently there are no other readers, which means that the write lock
		* needs to be acquired.
		*/
		DWORD r = WaitForSingleObject(rwlock->state_.write_semaphore_, 0);
		if (r == WAIT_OBJECT_0)
			rwlock->state_.num_readers_++;
		else if (r == WAIT_TIMEOUT)
			err = -1;
		else if (r == WAIT_FAILED)
			os_fatal_error(GetLastError(), "WaitForSingleObject");

	}
	else {
		/* The write lock has already been acquired because there are other
		* active readers.
		*/
		rwlock->state_.num_readers_++;
	}

	LeaveCriticalSection(&rwlock->state_.num_readers_lock_);
	return err;
}

void _rwlock_rdunlock(_rwlock_t* rwlock) {
	EnterCriticalSection(&rwlock->state_.num_readers_lock_);

	if (--rwlock->state_.num_readers_ == 0) {
		if (!ReleaseSemaphore(rwlock->state_.write_semaphore_, 1, NULL))
			os_fatal_error(GetLastError(), "ReleaseSemaphore");
	}

	LeaveCriticalSection(&rwlock->state_.num_readers_lock_);
}

void _rwlock_wrlock(_rwlock_t* rwlock) {
	DWORD r = WaitForSingleObject(rwlock->state_.write_semaphore_, INFINITE);
	if (r != WAIT_OBJECT_0)
		os_fatal_error(GetLastError(), "WaitForSingleObject");
}

 int _rwlock_trywrlock(_rwlock_t* rwlock) {
	DWORD r = WaitForSingleObject(rwlock->state_.write_semaphore_, 0);
	if (r == WAIT_OBJECT_0)
		return 0;
	else if (r == WAIT_TIMEOUT)
		return -1;
	else
		os_fatal_error(GetLastError(), "WaitForSingleObject");

	return -1;
}

void _rwlock_wrunlock(_rwlock_t* rwlock) {
	if (!ReleaseSemaphore(rwlock->state_.write_semaphore_, 1, NULL))
		os_fatal_error(GetLastError(), "ReleaseSemaphore");
}

#elif (TARGET_OS == OS_POSIX)

int _rwlock_init(_rwlock_t* rwlock) {
	return pthread_rwlock_init(rwlock, NULL);
}

void _rwlock_destroy(_rwlock_t* rwlock) {
	if (pthread_rwlock_destroy(rwlock))
		abort();
}

void _rwlock_rdlock(_rwlock_t* rwlock) {
	if (pthread_rwlock_rdlock(rwlock))
		abort();
}

int _rwlock_tryrdlock(_rwlock_t* rwlock) {
	int err;

	err = pthread_rwlock_tryrdlock(rwlock);
	if (err) {
		if (err != EBUSY && err != EAGAIN)
			abort();
		return -1;
	}

	return 0;
}

void _rwlock_rdunlock(_rwlock_t* rwlock) {
	if (pthread_rwlock_unlock(rwlock))
		abort();
}

void _rwlock_wrlock(_rwlock_t* rwlock) {
	if (pthread_rwlock_wrlock(rwlock))
		abort();
}

int _rwlock_trywrlock(_rwlock_t* rwlock) {
	int err;

	err = pthread_rwlock_trywrlock(rwlock);
	if (err) {
		if (err != EBUSY && err != EAGAIN)
			abort();
		return -1;
	}

	return 0;
}

void _rwlock_wrunlock(_rwlock_t* rwlock) {
	if (pthread_rwlock_unlock(rwlock))
		abort();
}

#elif (TARGET_OS == OS_DARWIN)

int _rwlock_init(_rwlock_t* rwlock) {
    return pthread_rwlock_init(rwlock, NULL);
}

void _rwlock_destroy(_rwlock_t* rwlock) {
    if (pthread_rwlock_destroy(rwlock))
        abort();
}

void _rwlock_rdlock(_rwlock_t* rwlock) {
    if (pthread_rwlock_rdlock(rwlock))
        abort();
}

int _rwlock_tryrdlock(_rwlock_t* rwlock) {
    int err;

    err = pthread_rwlock_tryrdlock(rwlock);
    if (err) {
        if (err != EBUSY && err != EAGAIN)
            abort();
        return -1;
    }

    return 0;
}

void _rwlock_rdunlock(_rwlock_t* rwlock) {
    if (pthread_rwlock_unlock(rwlock))
        abort();
}

void _rwlock_wrlock(_rwlock_t* rwlock) {
    if (pthread_rwlock_wrlock(rwlock))
        abort();
}

int _rwlock_trywrlock(_rwlock_t* rwlock) {
    int err;

    err = pthread_rwlock_trywrlock(rwlock);
    if (err) {
        if (err != EBUSY && err != EAGAIN)
            abort();
        return -1;
    }

    return 0;
}

void _rwlock_wrunlock(_rwlock_t* rwlock) {
    if (pthread_rwlock_unlock(rwlock))
        abort();
}

#endif


